#include "xp.h"

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

static void
debug(int n, XP_T x)
{
    for (int i = 0; i < n; i++)
        printf("%d ", x[i]);
    printf("\n");
}

static void
test_simple()
{
    unsigned long l;

    int n = (int)sizeof(long);
    XP_T x = (XP_T)malloc(n);
    
    l = XP_fromint(n, x, ULONG_MAX);
    assert(l == 0);
    assert(XP_length(n, x) == n);

    unsigned long l2 = XP_toint(n, x);
    assert(l2 == ULONG_MAX);

    assert(XP_cmp(n, x, x) == 0);

    free(x);
}

static void
test_add_sub()
{
    unsigned char z[]  = {  0,   0,   0,   0,   0, 0};
    unsigned char x[]  = {  1,   1,   1,   1,   1, 0};
    unsigned char x2[] = {255, 254, 254, 254, 254, 0};
    unsigned char x3[] = {  0,   0,   0,   0,   0, 1};
    int n = (int)sizeof(z);

    int carry = 0;
    carry = XP_add(n-1, z, x, x2, carry);
    // debug(n, z);
    assert(carry == 1);
    assert(XP_cmp(n-1, z, x3) == 0);

    int borrow = 0; 
    borrow = XP_sub(n, z, x3, x2, borrow);
    debug(n, z);
    assert(borrow == 0);
    assert(XP_cmp(n, z, x) == 0);

    borrow = 0;
    borrow = XP_sub(n-1, z, x3, x2, borrow);
    assert(borrow == 1);
    assert(XP_cmp(n-1, z, x) == 0);

    unsigned char x4[] = {255, 255, 255, 255, 255};
    carry = XP_sum(n-1, z, x4, 1);
    assert(carry == 1);
    assert(XP_cmp(n-1, z, x3) == 0);

    borrow = XP_diff(n-1, z, x3, 1);
    assert(carry == 1);
    assert(XP_cmp(n-1, z, x4) == 0);

    
    carry = 0;
    carry = XP_neg(n-1, z, x3, carry);
    assert(carry == 0);
    assert(XP_cmp(n-1, z, x4) == 0);

    carry = 1;
    carry = XP_neg(n-1, z, x3, carry);
    assert(carry == 1);
    assert(XP_cmp(n-1, z, x3) == 0);
}

static void
test_mul()
{
    unsigned char  z[] = {  0, 0, 0, 0, 0,
                            0, 0, 0, 0, 0,};
    unsigned char x1[] = {  1, 1, 1, 1, 1};
    unsigned char x2[] = {255, 0, 0, 0, 0};
    int n = (int)sizeof(x1);

    unsigned char x3[] = {  255, 255, 255, 255, 255,
                              0,   0,   0,   0,   0,};
    int carry = 0;
    carry = XP_mul(z, n, x1, n, x2);
    assert(carry == 0);
    assert(XP_cmp(2*n, z, x3) == 0);

    carry = XP_product(n, z, x1, 255);
    assert(carry == 0);
    assert(XP_cmp(n, z, x3) == 0);
}

static void
test_div()
{
    unsigned char q[] = {0, 0};
    unsigned char r[] = {0, 0};
    unsigned char x1[] = {1, 1};
    unsigned char x2[] = {4, 3};
    int n = (int)sizeof(x1);

    unsigned char tmp[] = {0, 0, 0,
                           0, 0, 0,};

    unsigned char q1[] = {3, 0};
    unsigned char r1[] = {1, 0};
    int flag;
    flag = XP_div(n, q, x2, n, x1, r, tmp);
    assert(flag == 1);
    assert(XP_cmp(n, q, q1) == 0); 
    assert(XP_cmp(n, r, r1) == 0); 

    int y = (int)XP_toint(n, x1);
    int remainder = XP_quotient(n, q, x2, y); 
    assert(XP_cmp(n, q, q1) == 0); 
    assert(remainder == 1);
}

static void
test_shift()
{
    unsigned char x[] = {0, 0, 0, 0};
    int n = (int)sizeof(x);
    unsigned char x1[] = {0xff, 0xff};
    int m = (int)sizeof(x1);

    unsigned char x2[] = {0, 0xf0, 0xff, 0xf};
    XP_lshift(n, x, m, x1, 12, 0);
    assert(XP_cmp(n, x, x2) == 0);

    unsigned char x2_1[] = {0, 0, 0, 0};
    XP_lshift(n, x, m, x1, 32, 0);
    assert(XP_cmp(n, x, x2_1) == 0);

    // Logic right shift
    unsigned char x3[] = {0xf, 0, 0, 0};
    XP_rshift(n, x, m, x1, 12, 0);
    assert(XP_cmp(n, x, x3) == 0);

    // Arithmetic right shift
    unsigned char x4[] = {0xff, 0x8f};
    unsigned char x5[] = {0xf8, 0xff, 0xff, 0xff};
    XP_rshift(n, x, m, x4, 12, 1);
    assert(XP_cmp(n, x, x5) == 0);
}

static void
test_str()
{
    const char *str = "257";
    unsigned char x[] = {0, 0};
    unsigned char x1[] = {1, 1};
    int n = (int)sizeof(x1);

    char *end;
    int carry;
    carry = XP_fromstr(n, x, str, 10, &end);
    assert(carry == 0);
    assert(XP_cmp(n, x, x1) == 0);
    assert(str + strlen(str) == end);

    char str2[] = {0, 0, 0, 0};
    char *p;
    p = XP_tostr(str2, (int)sizeof(str2), 10, n, x1); 
    assert(strcmp(str2, str) == 0);
    assert(strcmp(str2, p) == 0);
}

int main()
{
    test_simple();
    test_add_sub();
    test_mul();
    test_div();
    test_shift();
    test_str();
    return 0;
}

